package com.kingyea.gradle.task;


import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.gradle.api.DefaultTask;
import org.gradle.api.Project;
import org.gradle.api.tasks.TaskAction;

import com.kingyea.gradle.BuildException;
import com.kingyea.gradle.constant.ConstantValues;
import com.kingyea.gradle.extension.Deployer;
import com.kingyea.gradle.utils.FreeMarkerHelper;

import static com.kingyea.gradle.constant.ConstantValues.*;

public class LocalDeployTask extends DefaultTask {
	private List<Action> actions = new ArrayList<>();
	
	@TaskAction
	public void deploy() throws Exception {
		Deployer deployer = this.getProject().getExtensions().getByType(Deployer.class);
		File localDeployDir = new File(deployer.getDeployPath());
		if(localDeployDir.exists()) {
			System.out.println("clear local old deployment: " + deployer.getDeployPath());
			FileUtils.cleanDirectory(localDeployDir);
			System.out.println("clear local old deployment: " + deployer.getDeployPath() + " complete.");
		}
		
		for(Action action : this.actions) {
			action.execute();
		}
		System.out.println(String.format("local deployment to %s complete.", deployer.getDeployPath()));
	}
	
	
	public void copy(final String srcFile, final String destFile) throws Exception {
		this.actions.add(new Action() {
			@Override
			public void execute() throws Exception {
				System.out.println(String.format("copping file %s->%s", srcFile, destFile));
				FileUtils.copyFile(new File(srcFile), new File(destFile));
			}
		});
	}
	
	public void copyFiles(final String[] srcFiles, final String destDir) throws Exception {
		this.actions.add(new Action() {
			@Override
			public void execute() throws Exception {
				if(srcFiles!=null) {
					for(String srcFile : srcFiles) {
						System.out.println(String.format("copping file to directory %s->%s", srcFile, destDir));
						FileUtils.copyFileToDirectory(new File(srcFile), new File(destDir));
					}
				}
			}
		});
	}
	
	public void copyDir(final String srcDir, final String destDir) {
		this.actions.add(new Action() {
			@Override
			public void execute() throws Exception {
				System.out.println(String.format("copping directory %s->%s", srcDir, destDir));
				FileUtils.copyDirectory(new File(srcDir), new File(destDir));
			}
		});
	}
	
	/**
	 * copy project's all dependencies to the deployment directory's lib subdirectory.
	 * @param exclude   regular expression of the excluded dependency file's name
	 * @throws Exception
	 */
	public void copyAllDependencies(final String exclude) throws Exception {
		this.actions.add(new Action() {
			@Override
			public void execute() throws Exception {
				Project project = getProject();
				Deployer deployer = getDeployer();
				Set<File> files = project.getConfigurations()
						.getByName(ConstantValues.RUNTIME_DENPENDENCY_NAME).getFiles();
				for(File file : files) {
					if(!Pattern.matches(exclude, file.getName())) {
						String dest = deployer.getLibDeployPath() +"/"+ file.getName();
						//invoke LocalDeployTask.this.copy(file.getAbsolutePath(), dest) method here
						//only add a Action to the actions list, it does not execute the added action,
						//and when we iterate actions list, we modify it too. so a ConcurrentModificationException
						//exception will throw, if you want to avoid this exception, you can replace the ArrayList class
						//with CopyOnWriteArrayList.
						System.out.println(String.format("copping dependency %s->%s", file.getAbsolutePath(), dest));
						FileUtils.copyFile(file, new File(dest));
					}
				}
			}
		});
	}
	
	public void copyConfigFile(final String srcFile, final String destFile) {
		this.copyConfigFile(srcFile, destFile, null, false);
	}
	
	public void copyConfigFile(final String sourceFile, final String destFile, final Map<String, Object> configs) {
		this.copyConfigFile(sourceFile, destFile, configs, false);
	}
	
	/**
	 * copy configure file
	 * @param srcFile source configure file's freemarker template
	 * @param destFile the output file after evaluate freemarker template
	 * @param configs additional configures, add to the freemarker evaluation context
	 * @param replaceBreak weather replace break line character
	 */
	public void copyConfigFile(final String srcFile, final String destFile, 
			final Map<String, Object> configs, final boolean replaceBreak) {
		this.actions.add(new Action() {
			@Override
			public void execute() throws Exception {
				System.out.println(String.format("copping config file(eval freemarker template) %s->%s", srcFile, destFile));
				File file = new File(srcFile);
				if(!file.exists()) {
					throw new BuildException(String.format("config file: %s does not exists.", file.getAbsolutePath()));
				}
				
				File outputFile = new File(destFile);
				File parent = outputFile.getParentFile();
				if(!parent.exists()) {
					parent.mkdirs();
				}
				
				Map<String, Object> contextMap = createContextMap(configs);
				
				FileInputStream input = new FileInputStream(file);
				FileOutputStream output = new FileOutputStream(outputFile);
				FreeMarkerHelper.evalFreeMarker(null, contextMap, input, output, replaceBreak);
				IOUtils.closeQuietly(input);
				IOUtils.closeQuietly(output);
			}
		});
	}
	
	
	
	private Deployer getDeployer() {
		return this.getProject().getExtensions().getByType(Deployer.class);
	}
	
	
	/**
	 * create freemarker evaluation context
	 * @param configs additional configures, add to the freemarker evaluation context
	 * @return
	 */
	private Map<String, Object> createContextMap(Map<String, Object> configs) {
		Map<String, Object> contextMap = new HashMap<>();
		Deployer deployer = getDeployer();
		contextMap.put(KEY_TARGET_OS, deployer.getTargetOs());
		contextMap.put(KEY_TARGET_OS_ARCH, deployer.getTargetOsArch());
		contextMap.put(KEY_DISTRIBUTION, deployer.getDistribution());
		contextMap.put(KEY_DEPLOY_PATH, deployer.getDeployPath());
		contextMap.put(KEY_ETC_PATH, deployer.getEtcDeployPath());
		contextMap.put(KEY_RESOURCE_PATH, deployer.getResourcePath());
		contextMap.put(KEY_LIB_DEPLOY_PATH, deployer.getLibDeployPath());
		contextMap.put(KEY_VAR_PATH, deployer.getVarPath());
		
		Map<String, Object> deployConfigs = new HashMap<>();
		if(deployer.getGlobalDeployConfigs()!=null) {
			deployConfigs.putAll(deployer.getGlobalDeployConfigs());
		}
		if(OS_WIN.equals(deployer.getTargetOs()) && deployer.getWindowsDeployConfigs()!=null) {
			deployConfigs.putAll(deployer.getWindowsDeployConfigs());
		}
		if(OS_LINUX.equals(deployer.getTargetOs()) && deployer.getLinuxDeployConfigs()!=null) {
			deployConfigs.putAll(deployer.getLinuxDeployConfigs());
		}
		contextMap.put(KEY_DEPLOY_CONFIGS, deployConfigs);
		
		if(configs!=null) {
			contextMap.putAll(configs);
		}
		return contextMap;
	}
	
	public static interface Action {
		void execute() throws Exception;
	}
}
